#!/bin/sh
#
# fesh -- An Atom feed generator for Gemini capsules written in POSIX sh.
#
# Copyright (C) 2024  Ricardo García Jiménez <ricardogj08@riseup.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

FESH_VERSION=3.0
FESH_REPO=https://notabug.org/ricardogj08/fesh

# Muestra mensajes de errores.
error() {
  printf '%s.\n' "$1" >&2
  exit 1
}

# Muestra un mensaje de ayuda.
shelp() { printf '%s' "\
fesh ${FESH_VERSION} - An Atom feed generator for Gemini capsules written in POSIX sh.

Synopsis:
  fesh [OPTIONS]

Options:
  -a <STRING> - Author's name [default: fesh].
  -c <STRING> - Capsule name [default: fesh].
  -d <STRING> - Capsule domain name [default: localhost].
  -l <STRING> - Capsule language [default: es].
  -n <NUMBER> - Maximum number of entries [default: 64].
  -o <PATH>   - Output directory [default: .].
  -r <PATH>   - Capsule root directory [default: .].
  -t <STRING> - Capsule license [default: CC-BY-4.0].

Example:
  fesh -d myblog.com -c 'My blog'
"
exit 0
}

# Genera el dateatom del tiempo actual.
current_dateatom() {
  LC_ALL=C date -u '+%Y-%m-%dT%H:%M:%SZ'
}

# Genera el encabezado del feed de Atom de la cápsula.
open_atomfeed() { cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${FESH_LANG}">
  <title>${FESH_CAPSULE}</title>
  <link href="${BASE_URL}/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="${BASE_URL}" rel="alternate" type="text/gemini"/>
  <updated>$(current_dateatom)</updated>
  <id>${BASE_URL}/atom.xml</id>
  <author>
    <name>${FESH_AUTHOR}</name>
    <uri>${BASE_URL}</uri>
  </author>
  <rights>${FESH_LICENSE}</rights>
  <generator uri="${FESH_REPO}" version="${FESH_VERSION}">
    fesh
  </generator>
EOF
}

# Genera la etiqueta de cierre del feed de Atom de la cápsula.
close_atomfeed() {
  printf '</feed>\n'
}

# Obtiene el dateatom de la fecha de modificación de un archivo.
file_dateatom() {
  LC_ALL=C date -u -r "$1" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null
}

# Genera una entrada para el feed de Atom de la cápsula.
entry_atomfeed() { cat <<EOF
  <entry>
    <title>${ENTRY_TITLE}</title>
    <link href="${ENTRY_URL}" rel="alternate" type="text/gemini"/>
    <id>${ENTRY_URL}</id>
    <updated>${ENTRY_UPDATED}</updated>
    <author>
      <name>${FESH_AUTHOR}</name>
      <uri>${BASE_URL}</uri>
    </author>
  </entry>
EOF
}

# Elimina todos los espacios sobrantes de un string.
# shellcheck disable=SC2048,SC2086
trim_all() {
  set -f

  set -- $*

  printf '%s' "$*"

  set +f
}

# Genera el feed de Atom de la cápsula.
generate_atomfeed() {
  open_atomfeed

  # Busca todos los archivos *.gmi y *.gemini
  # e ignora los archivos index.gmi e index.gemini,
  # ordenados por fecha de modificación.
  (
    cd "${FESH_ROOT}" ||
      error 'Could not access capsule root directory'

    itr=0

    (
      find . -type f \( -name \*.gmi -o -name \*.gemini \) \
        -a ! \( -name index.gmi -o -name index.gemini \) \
        -exec ls -t {} + ||
          error 'Could not search Gemini files'
    ) | while IFS= read -r file; do
      [ "${itr}" -ge "${FESH_NUMBER}" ] && break

      # Contruye la URL del archivo.
      ENTRY_URL=${BASE_URL}/${file#./}

      # Obtiene el primer título del archivo.
      ENTRY_TITLE=$(grep -se '^#[[:space:]]' "${file}" | head -n 1)
      ENTRY_TITLE=${ENTRY_TITLE#\#[[:space:]]}
      ENTRY_TITLE=$(trim_all "${ENTRY_TITLE}")

      # Obtiene la fecha de modificación del archivo.
      ENTRY_UPDATED=$(file_dateatom "${file}" || current_dateatom)

      entry_atomfeed

      itr=$((itr + 1))
    done
  )

  close_atomfeed
}

# Función principal de la aplicación.
main() {
  while getopts :a:c:d:l:n:o:r:t: option; do
    case $option in
      a) FESH_AUTHOR=${OPTARG};;
      c) FESH_CAPSULE=${OPTARG};;
      d) FESH_DOMAIN=${OPTARG};;
      l) FESH_LANG=${OPTARG};;
      n) FESH_NUMBER=${OPTARG};;
      o) FESH_OUTPUT=${OPTARG};;
      r) FESH_ROOT=${OPTARG};;
      t) FESH_LICENSE=${OPTARG};;
      \?) shelp;;
      :) error "Option -${OPTARG} requires an argument"
    esac
  done

  # Decrementa el puntero del argumento "$n"
  # para que apunte al siguiente argumento.
  shift $((OPTIND - 1))

  # Configuración por defecto.
  : "${FESH_CAPSULE=fesh}"
  : "${FESH_AUTHOR:=$FESH_CAPSULE}"
  : "${FESH_DOMAIN:=localhost}"
  : "${FESH_LANG:=es}"
  : "${FESH_NUMBER:=64}"
  : "${FESH_OUTPUT:=.}"
  : "${FESH_ROOT:=.}"
  : "${FESH_LICENSE:=CC-BY-4.0}"

  # Elimina diagonales sobrantes del final.
  FESH_DOMAIN=${FESH_DOMAIN%"${FESH_DOMAIN##*[!/]}"}

  # Construye la URL base de la cápsula.
  BASE_URL=gemini://${FESH_DOMAIN%%/*}:1965
  BASE_URL=${BASE_URL}${FESH_DOMAIN#"${FESH_DOMAIN%%/*}"}

  :> "${FESH_OUTPUT}/atom.xml" ||
    error 'Could not create Atom feed file'

  generate_atomfeed > "${FESH_OUTPUT}/atom.xml"
}

main "$@"
